#region Using directives

using System;
using System.Collections.Generic;
using System.Text;

using Weazel.Gezel.Model.Errors;

#endregion

namespace Weazel.Gezel.Model
{
    public partial class Datapath : IVerifiable
    {
        /// <summary>
        /// 
        /// </summary>
        /// <param name="errors"></param>
        /// <param name="arguments">optional custom verification arguments</param>
        public void Verify(ref List<VerificationError> errors, ref List<VerificationWarning> warnings, params object[] arguments)
        {
            // check that the name of this dp is ok 
            Identifier.Verify(ref errors, ref warnings, localName, StartPosition);

            System.Diagnostics.Debug.Assert(this.Model != null);

            verifyTracers(errors, ref warnings);

            verifySignalFlowGraphs(ref errors, ref warnings);

            verifyChildDatapaths(errors, ref warnings);

            verifyOutports(ref errors, ref warnings);

            verifySignalsAndRegisters(ref errors, ref warnings);
        }

        private void verifyOutports(ref List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {
            foreach (OutPort outport in OutPorts.Values)
                outport.Verify(ref errors, ref warnings);
        }

        private void verifyLookUpTables(ref List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {
            foreach (LookupTable table in LookupTables.Values)
                table.Verify(ref errors, ref warnings);
        }

        private void verifySignalsAndRegisters(ref List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {
            foreach (Signal signal in signals.Values)
                signal.Verify(ref errors, ref warnings);

            foreach (Register register in registers.Values)
                register.Verify(ref errors, ref warnings);
        }

        private void verifySignalFlowGraphs(ref List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {
            foreach (SignalFlowGraph sfg in signalFlowGraphs.Values)
            {
                sfg.Verify(ref errors, ref warnings, null);
            }
        }

        private void verifyTracers(List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {
            foreach (SignalTrace tracer in tracers)
            {
                // check expression to trace
                tracer.Expression.Verify(ref errors, ref warnings, this);

                // check that the filename is not already being used by another signal tracer.
                if (Model.FileToTracerMap.ContainsKey(tracer.Filename))
                {
                    ErrorMessage message = new ErrorMessage();
                    message.AddString("Output file used in signal trace at ");
                    message.AddPosition(tracer.StartPosition);
                    message.AddString(" also used in signal trace at ");
                    message.AddPosition(Model.FileToTracerMap[tracer.Filename].StartPosition);

                    errors.Add(
                      new VerificationError(
                        VerificationErrorType.MultipleUsesOfTracerFile,
                        message
                      )
                    );
                }
                else
                {
                    Model.FileToTracerMap.Add(tracer.Filename, tracer);
                }
            }
        }

        private void verifyChildAsDatapath(
            ref List<VerificationError> errors,
            ref List<VerificationWarning> warnings,
            string childDatapathRef
        )
        {
            Datapath child = children[childDatapathRef] = (parent as Model).Datapaths[childDatapathRef];

            string[] portRefs = childDatapathRefs[childDatapathRef];
            for (int i = 0; i < portRefs.Length; i++)
            {
                Net net = new Net(this, portRefs[i]);
                nets.Add(net);

                Port port = child.GetPortAtPosition(i);

                if (port == null)
                {                    
                    int childPortCount = child.InPorts.Count + child.OutPorts.Count;

                    if (i >= childPortCount)
                    {
                        ErrorMessage message = new ErrorMessage();
                        message.AddString("Port index out-of-bounds. Trying to connect net '" + portRefs[i]);
                        message.AddString("' to non-existing port at declaration position " + i);
                        message.AddString(" of child datapath '");
                        message.AddEntity(child);
                        message.AddString("' at ");
                        message.AddPosition(child.startPosition);
                        message.AddString(".");

                        errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
                    }
                    else
                    {
                        errors.Add(new VerificationError(VerificationErrorType.UnspecifiedError));
                    }
                }
                else if (port is InPort)
                {
                    net.InPort = port;
                    CommonEntity source = this.GetIdentifier(portRefs[i]);

                    if (source is Port || source is Signal || source is Register)
                        net.OutPort = source;
                    else
                    {
                        ErrorMessage message = new ErrorMessage();
                        message.AddString("Unacceptable input '" + portRefs[i] + "' for positional binding. ");
                        message.AddString("Expected register, signal or port.");
                        // todo: add position information (when available)

                        errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
                    }
                }
                else if (port is OutPort)
                {
                    net.OutPort = port;
                    // TODO: add verification that result is port or signal
                    CommonEntity sink = this.GetIdentifier(portRefs[i]);

                    if (sink is Port || sink is Signal || sink is Register)
                        net.InPort = sink;
                    else
                    {
                        ErrorMessage message = new ErrorMessage();
                        message.AddString("Unacceptable input '" + portRefs[i] + "' for positional binding. ");
                        message.AddString("Expected register, signal or port.");
                        // todo: add position information (when available)

                        errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
                    }
                }
            }
        }

        private void verifyChildAsIpBlock(
            ref List<VerificationError> errors,
            ref List<VerificationWarning> warnings,
            string childIpBlockRef
        )
        {
            IPBlock child = childIpBlocks[childIpBlockRef] = (parent as Model).IPBlocks[childIpBlockRef];

            string[] portRefs = childDatapathRefs[childIpBlockRef];
            for (int i = 0; i < portRefs.Length; i++)
            {
                Net net = new Net(this, portRefs[i]);
                nets.Add(net);

                Port port = child.GetPortAtPosition(i);

                if (port == null)
                {
                    int childPortCount = child.inPorts.Count + child.outPorts.Count;

                    if (i >= childPortCount)
                    {
                        ErrorMessage message = new ErrorMessage();
                        message.AddString("Port index out-of-bounds. Trying to connect net '" + portRefs[i]);
                        message.AddString("' to non-existing port at declaration position " + i);
                        message.AddString(" of child datapath '");
                        message.AddEntity(child);
                        message.AddString("' at ");
                        message.AddPosition(child.StartPosition);
                        message.AddString(".");

                        errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
                    }
                    else
                    {
                        errors.Add(new VerificationError(VerificationErrorType.UnspecifiedError));
                    }
                }
                else if (port is InPort)
                {
                    net.InPort = port;
                    CommonEntity source = this.GetIdentifier(portRefs[i]);

                    if (source is Port || source is Signal || source is Register)
                        net.OutPort = source;
                    else
                    {
                        ErrorMessage message = new ErrorMessage();
                        message.AddString("Unacceptable input '" + portRefs[i] + "' for positional binding. ");
                        message.AddString("Expected register, signal or port.");
                        // todo: add position information (when available)

                        errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
                    }
                }
                else if (port is OutPort)
                {
                    net.OutPort = port;
                    // TODO: add verification that result is port or signal
                    CommonEntity sink = this.GetIdentifier(portRefs[i]);

                    if (sink is Port || sink is Signal || sink is Register)
                        net.InPort = sink;
                    else
                    {
                        ErrorMessage message = new ErrorMessage();
                        message.AddString("Unacceptable input '" + portRefs[i] + "' for positional binding. ");
                        message.AddString("Expected register, signal or port.");
                        // todo: add position information (when available)

                        errors.Add(new VerificationError(VerificationErrorType.SystemInvalidPort, message));
                    }
                }
            }
        }

        private void verifyChildDatapaths(List<VerificationError> errors, ref List<VerificationWarning> warnings)
        {
            foreach (string childDatapathRef in childDatapathRefs.Keys)
            {                
                Model model = parent as Model;

                if (model.Datapaths.ContainsKey(childDatapathRef))
                {
                    verifyChildAsDatapath(ref errors, ref warnings, childDatapathRef);
                }
                else if (model.IPBlocks.ContainsKey(childDatapathRef))
                {
                    verifyChildAsIpBlock(ref errors, ref warnings, childDatapathRef);
                }
                else
                {
                    // this situation is handled elsewhere
                    // skip to next entry
                    continue;
                }
            }
        }
    } // end class
}
